package com.monochromebytes.mononet.stub;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;

import com.monochromebytes.mononet.virtual.Connection;
import com.monochromebytes.mononet.virtual.IncomingDataPacket;
import com.monochromebytes.mononet.virtual.OutgoingDataPacket;
import com.monochromebytes.mononet.virtual.PacketListener;
import com.monochromebytes.mononet.virtual.VirtualAddress;

public class Stub implements PacketListener {

    private final HashMap<OutgoingDataPacket, Object> sentObjects;
    private final ArrayList<ObjectListener>           objectListeners;
    private final Serializer                          serializer;

    private final Connection                          connection;
    private final StubSocket                          socket;

    private long                                      lastReceivedObjectTimestamp;

    protected Stub(Connection connection, Serializer serializer, StubSocket socket) {
        this.connection = connection;
        this.serializer = serializer;
        this.socket = socket;

        this.connection.addPacketListener(this);
        this.sentObjects = new HashMap<OutgoingDataPacket, Object>();
        this.objectListeners = new ArrayList<ObjectListener>();
        this.lastReceivedObjectTimestamp = System.currentTimeMillis();
    }

    public void removeFromSocket() {
        this.socket.removeStub(this);
        this.connection.removeFromSocket();
    }
    
    public void send(Object object, int timeToLive) throws IOException {
        Connection connection = this.getConnection();
        ByteBuffer data = this.serializer.serialize(object, this);
        OutgoingDataPacket packet = connection.sendPacket(data, timeToLive);
        synchronized (this.sentObjects) {
            this.sentObjects.put(packet, object);
        }
    }

    public void checkForLostObjects() {
        this.connection.checkForLostPackets();
    }

    public void handleNextObject() {
        this.connection.handleNextPacket();
    }

    public boolean hasIncomingObjects() {
        return this.connection.hasIncomingPackets();
    }

    public void clearInbox() {
        this.connection.clearInbox();
    }

    @Override
    public void receivePacket(IncomingDataPacket packet, Connection connection) {
        this.lastReceivedObjectTimestamp = System.currentTimeMillis();
        Object object = this.serializer.deserialize(packet.getData(), this);
        synchronized (this.objectListeners) {
            for (ObjectListener listener : this.objectListeners) {
                listener.receiveObject(object, this);
            }
        }
    }

    @Override
    public void lostPacket(OutgoingDataPacket packet, Connection connection) {
        Object object;
        object = this.sentObjects.remove(packet);
        int timeToLive = packet.getTimeToLive();
        synchronized (this.objectListeners) {
            for (ObjectListener listener : this.objectListeners) {
                listener.lostObject(object, this, timeToLive);
            }
        }
    }

    @Override
    public void acknowledgedPacket(OutgoingDataPacket packet, Connection connection) {
        Object object;
        synchronized (this.sentObjects) {
            object = this.sentObjects.remove(packet);
        }
        int timeToLive = packet.getTimeToLive();
        int roundTripTime = (int) (System.currentTimeMillis() - packet.getTimestampOfOutput());
        synchronized (this.objectListeners) {
            for (ObjectListener listener : this.objectListeners) {
                listener.acknowledgedObject(object, this, timeToLive, roundTripTime);
            }
        }
    }

    public void addObjectListener(ObjectListener listener) {
        synchronized (this.objectListeners) {
            this.objectListeners.add(listener);
        }
    }

    public void removeObjectListener(ObjectListener listener) {
        synchronized (this.objectListeners) {
            this.objectListeners.remove(listener);
        }
    }

    public Connection getConnection() {
        return this.connection;
    }

    public StubSocket getSocket() {
        return this.socket;
    }

    public VirtualAddress getAddress() {
        return this.connection.getAddress();
    }
    
    public long getLastReceivedObjectTimestamp() {
        return this.lastReceivedObjectTimestamp;
    }
}
